const http = require('http');
// 粉笔 设置终端显示文字颜色
const chalk = require('chalk');
const url = require('url');
const path = require('path');
const fs = require('fs').promises;
const { createReadStream, createWriteStream, readFileSync, readdirSync } = require('fs');
// 获取文件类型的Content-type
const mime = require('mime');
// 服务端读取目录进行渲染
const ejs = require('ejs');
// 文件压缩
const zlib = require('zlib');
// 文件加密
const crypto = require('crypto');

const template = readFileSync(path.resolve(__dirname, 'templete.ejs'), 'utf8');
class Server{
    constructor(config){
        this.port = config.port;
        this.host = config.host;
        this.directory = config.directory;
        this.template = template
    }
    async handleRequest(req,res){
        // 获取请求路劲
        let { pathname } = url.parse(req.url);
        // 转义路劲中包含的中文内容
        pathname = decodeURIComponent(pathname);
        const filePath = path.join(this.directory,pathname);

        try {
            // 通过路劲查找文件
            const stat = await fs.stat(filePath);
            // 判断文件类型 文件直接返回 目录通过模板文件返回路劲列表
            if(stat.isFile()){
                this.sendFile(req, res, filePath, stat);
                return;
            }
            
            try {
                const contentFilePath = path.join(filePath, 'index.html');
                const contentStat = await fs.stat(contentFilePath);
                this.sendFile(req, res, contentFilePath, contentStat);
            } catch (error) {
                // 渲染目录树
                this.renderList(req, res, filePath, stat, pathname)
            }

        } catch (error) {
            this.sendError(req, res, error);
        }

        
    }
    compress(req, res){
        if(!req.headers['accept-encoding']){
            return;
        }
        if(req.headers['accept-encoding'].includes('gzip')){
            res.setHeader('Content-Encoding', 'gzip');
            return zlib.createGzip();
        }
        if(req.headers['accept-encoding'].includes('deflate')){
            res.setHeader('Content-Encoding', 'deflate');
            return zlib.createDeflate();
        }

    }
    cache(req, res, filePath, stat){
        const ifModifiedSince = req.headers['if-modified-since'];
        const ifNoneMatch = req.headers['if-none-match'];
        // 设置最大过期时间
        res.setHeader('Expires', new Date(Date.now()+10*1000).toGMTString());
        // 设置过期时间
        res.setHeader('Cache-Control', 'max-age=10');
        const ctime = stat.ctime.toGMTString();
        res.setHeader('Last-modified', ctime);
        const fileContent = readFileSync(filePath);
        const etag = crypto.createHash('md5').update(fileContent).digest('base64');
        res.setHeader('Etag', etag);

        if(etag !== ifNoneMatch){
            return false;
        }
        if(ctime !== ifModifiedSince){
            return false;
        }
        return true;
    }
    sendFile(req, res, filePath, stat){
        try {
            const cache = this.cache(req, res, filePath, stat);
            if(cache){
                res.stateCode = 304;
                return res.end();
            }
        } catch (error) {
            console.log(error);
        }

        res.setHeader('Content-type', `${mime.getType(filePath)}; charset=utf-8`);
        const compress = this.compress(req, res);
        if(compress){
            createReadStream(filePath).pipe(compress).pipe(res);
        }else{
            createReadStream(filePath).pipe(res);
        }
    }
    async renderList(req, res, filePath, stat, pathname){
        let dirs = await fs.readdir(filePath);
        try {
            const list = dirs.map((dir)=>({
                dir,
                href: path.join(pathname, dir)
            }));
            // ejs 渲染模板
            const temp = await ejs.render(template,{dirs:list},{async:true});
            res.end(temp);
        } catch (error) {
            this.sendError(req, res, error);
        }
    }
    sendError(req, res, error){
        res.stateCode = 404;
        res.end('File Is Not Found!');
    }
    start(){
        const server = http.createServer(this.handleRequest.bind(this));
        server.listen(this.port, this.host, ()=> {
            const dir = chalk.cyan(`./${this.directory.split('\\').pop()}`);
            console.log(chalk.yellow(`Starting up lv-server, serving`, dir));
            console.log(chalk.yellow(`Available on:`));
            console.log(chalk.green(`    http://${this.host}:${this.port}`));
        });
    }
}

module.exports = Server;